﻿using Candy.Infrastructure;
using Candy.Model;
using GalaSoft.MvvmLight.Command;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Net;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Linq;
using GalaSoft.MvvmLight.Threading;
using Candy.Web.Data;
using System.Threading;

namespace Candy.ViewModel
{
    public class ImportViewModel : CandyViewModelBase
    {
        private bool _isBusy;
        public bool IsBusy
        {
            get
            {
                return _isBusy;
            }
            set
            {
                _isBusy = value;
                RaisePropertyChanged("IsBusy");
                ValidateImport.RaiseCanExecuteChanged();
                Import.RaiseCanExecuteChanged();
            }
        }

        private ImportOptionsViewModel _options;
        public ImportOptionsViewModel Options
        {
            get
            {
                return _options;
            }
            set
            {
                _options = value;
                RaisePropertyChanged("Options");
            }
        }

        private string _importData;
        public string ImportData
        {
            get
            {
                return _importData;
            }
            set
            {
                _importData = value;
                RaisePropertyChanged("ImportData");
                ValidateImport.RaiseCanExecuteChanged();
            }
        }

        private ObservableCollection<ImportOperation> _operations;
        public ObservableCollection<ImportOperation> Operations
        {
            get
            {
                return _operations;
            }
            set
            {
                _operations = value;
                RaisePropertyChanged("Operations");
            }
        }


        private bool _importIsValid;
        public bool ImportIsValid
        {
            get
            {
                return _operations != null && _operations.Count > 0 && _operations.Any(x => x.Action == ImportAction.Fail) == false;
            }

        }



        IDataService _dataService;
        INavigationService _navigationService;
        IImportService _importService;
        public ImportViewModel(IDataService dataService, INavigationService navigiationSerivce, IImportService importService)
        {
            _dataService = dataService;
            _navigationService = navigiationSerivce;
            _importService = importService;
            Options = new ImportOptionsViewModel()
            {
                AllowNewDatabases = true,
                AllowNewSpecies = true,
                AutoCreateFamilyPath = true


            };
        }



        private RelayCommand _validateImport;
        public RelayCommand ValidateImport
        {
            get
            {
                return _validateImport ?? (_validateImport = new RelayCommand(ExecValidateImport, CanValidateImport));
            }
        }

        /// <summary>
        /// Checks whether the ValidateImport command is executable
        /// </summary>
        private bool CanValidateImport()
        {
            return !IsBusy && !string.IsNullOrEmpty(ImportData);
        }

        /// <summary>
        /// Executes the ValidateImport command 
        /// </summary>
        private async void ExecValidateImport()
        {
            //FamilyPath	Id	Name	SPecies	Database	Sequence	Description
            //FamilyA\FamilyA1	Poptr000213	Alax1	Arabidopsis thaliana	Poptri3	ATGCATG	SomeDescription bla bla	
            //FamilyPath seperator is \
            //Family at this path must exist or AutoCreate family on
            //If AutoCreate Family Name must be valid 
            //Id - if exist then skip
            //Name - if exist the skip
            //Species  must exist or AllowNew species option on
            //if create new species then naming must be valid
            //Database must exist or AllowNew database option on
            //if create new database then naming must be valid
            //Sequence is required and needs a valid protein sequence
            //Description - no requirements

            IsBusy = true;
            Operations = new ObservableCollection<ImportOperation>();
            var importResult = await _importService.ReadAsync(ImportData);
            if (!importResult.HasErrors)
            {
                await ValidateFamiliesAsync(importResult.Items.Select(x => x.FamilyPath));
                await ValidateSpeciesAsync(importResult.Items.Select(x => x.Species));
                
            }
            RaisePropertyChanged("ImportIsValid");
            IsBusy = false;

        }


        private RelayCommand _import;
        public RelayCommand Import
        {
            get
            {
                return _import ?? (_import = new RelayCommand(ExecImport, CanImport));
            }
        }

        /// <summary>
        /// Checks whether the Import command is executable
        /// </summary>
        private bool CanImport()
        {
            return IsBusy == false && ImportIsValid;
        }

        /// <summary>
        /// Executes the Import command 
        /// </summary>
        private async void ExecImport()
        {
            IsBusy = true;
            var familyOperations = _operations.Where(x => x.Action == ImportAction.Create && x.PayloadType == PayloadType.Family);
            var specOperations = _operations.Where(x => x.Action == ImportAction.Create && x.PayloadType == PayloadType.Species);
            await ImportFamiliesAsync(familyOperations);
            await ImportSpeciesAsync(specOperations);
            IsBusy = false;
        }


        AutoResetEvent _famImportEvent = new AutoResetEvent(false);
        private async Task ImportFamiliesAsync(IEnumerable<ImportOperation> familyOperations)
        {
            await Task.Factory.StartNew(() =>
               {

                   foreach (var item in familyOperations)
                   {
                       string[] famArr = item.Payload.Split("\\".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                       FamilyDto parentFam = null;
                       for (int i = 0; i < famArr.Length; i++)
                       {
                           FamilyDto fam = null;
                           fam = _dataService.Families.SingleOrDefault(x => x.Name == famArr[i]);
                           if (fam == null)
                           {
                               fam = new FamilyDto

                              {
                                  ProjectId = _dataService.LoadedProject.Id,
                                  CreateDate = DateTime.Now,
                                  Type = FamilyType.family,
                                  Name = famArr[i]
                              };

                               _dataService.Families.Add(fam);
                               if (parentFam != null)
                               {
                                   fam.ParentId = parentFam.Id;
                                   parentFam.SubFamilies.Add(fam);
                               }
                               _dataService.SubmitAsync((op) =>
                                 {

                                     if (op.HasError)
                                         base.HandleDomainError(op, "Import family failed");
                                     else
                                         item.Action = ImportAction.Ready;

                                     _famImportEvent.Set();
                                 });
                               _famImportEvent.WaitOne();
                           }
                           parentFam = fam;

                       }


                   }

               });

        }

        private async Task ValidateFamiliesAsync(IEnumerable<string> familyPathes)
        {

            await Task.Factory.StartNew(() =>
                 {
                     foreach (var famPath in familyPathes.Distinct())
                     {
                         List<ImportOperation> actions = new List<ImportOperation>();
                         ImportOperation ia = new ImportOperation();
                         ia.PayloadType = PayloadType.Family;
                         ia.Payload = famPath;

                         bool pathExists = true;
                         //foreach (var item in fams)
                         //{
                         pathExists = pathExists && _dataService.Families.Any(x => x.Path.ToLower() == famPath.ToLower());
                         //}
                         if (Options.AutoCreateFamilyPath)
                         {
                             if (pathExists)
                             {
                                 ia.Action = ImportAction.Ready;
                                 ia.Message = "Family exists. No action needed.";
                             }
                             else
                             {
                                 ia.Action = ImportAction.Create;
                                 ia.Message = "Family does not exists and will be created.";
                             }
                         }
                         else
                         {
                             if (pathExists)
                             {
                                 ia.Action = ImportAction.Ready;
                                 ia.Message = "Family exists. No action needed.";
                             }
                             else
                             {
                                 ia.Action = ImportAction.Fail;
                                 ia.Message = "Family does not exists. Import can't be executed. Change import options to allow auto create families or create family manually.";
                             }
                         }
                         if (ia.Action == ImportAction.Create)
                         {

                             string[] fams = famPath.Split("\\".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                             foreach (var item in fams)
                             {
                                 FamilyDto famdto = new FamilyDto();
                                 famdto.Name = item;
                                 if (famdto.HasValidationErrors)
                                 {
                                     ia.Action = ImportAction.Fail;
                                     ia.Message = famdto.ValidationErrors.First().ErrorMessage;
                                 }
                             }


                         }
                         DispatcherHelper.CheckBeginInvokeOnUI(() =>
                             {
                                 Operations.Add(ia);
                               
                             });
                     }


                 });
            return;
        }


        AutoResetEvent _specImportEvent = new AutoResetEvent(false);
        private async Task ImportSpeciesAsync(IEnumerable<ImportOperation> speciesOperations)
        {
            await Task.Factory.StartNew(() =>
            {

                foreach (var item in speciesOperations)
                {
                    string[] specArr = item.Payload.Split("".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                    string specName;
                    bool nameFound = Candy.Web.Model.Autonaming.NameGenotype(_dataService.Genotypes.Select(x => x.Name).ToList(), specArr[0].Trim(), specArr[1], out specName);
                    if (nameFound)
                    {
                        GenotypeDto spec = new GenotypeDto
                        {
                            ProjectId = _dataService.LoadedProject.Id,
                            Genus = specArr[0].Trim(),
                            Species = specArr[1].Trim(),
                            Name = specName
                        };

                        _dataService.Genotypes.Add(spec);
                        _dataService.SubmitAsync((op) =>
                         {

                             if (op.HasError)
                                 base.HandleDomainError(op, "Import species failed");
                             else
                                 item.Action = ImportAction.Ready;

                             _specImportEvent.Set();
                         });
                        _specImportEvent.WaitOne();
                    }
                    else
                    {
                        item.Action = ImportAction.Fail;
                        item.Message = "Couldn't find a name for species.";
                    }
                }

            });

        }

        private async Task ValidateSpeciesAsync(IEnumerable<string> specieses)
        {

            await Task.Factory.StartNew(() =>
            {
                foreach (var spec in specieses.Distinct())
                {
                    List<ImportOperation> actions = new List<ImportOperation>();
                    ImportOperation ia = new ImportOperation();
                    ia.PayloadType = PayloadType.Species;
                    ia.Payload = spec;
                    string[] specArr = spec.Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                    if (specArr.Length != 2)
                    {
                        ia.Action = ImportAction.Fail;
                        ia.Message = "Wrong species format. Needs Genus and species. e.g. Arabidopsis thaliana";
                    }
                    else
                    {
                        string genus = specArr[0];
                        string species = specArr[1];
                        bool specExists = true;
                        specExists = specExists && _dataService.Genotypes.Any(x => x.Genus.ToLower() == genus.ToLower() && x.Species.ToLower() == species.ToLower());

                        if (Options.AllowNewSpecies)
                        {
                            if (specExists)
                            {
                                ia.Action = ImportAction.Ready;
                                ia.Message = "Species exists. No action needed.";
                            }
                            else
                            {
                                ia.Action = ImportAction.Create;
                                ia.Message = "Species does not exists and will be created.";
                            }
                        }
                        else
                        {
                            if (specExists)
                            {
                                ia.Action = ImportAction.Ready;
                                ia.Message = "Species exists. No action needed.";
                            }
                            else
                            {
                                ia.Action = ImportAction.Fail;
                                ia.Message = "Species does not exists. Import can't be executed. Change import options to allow auto create species or create species manually.";
                            }
                        }
                        if (ia.Action == ImportAction.Create)
                        {
                            string specName;
                            bool nameFound = Candy.Web.Model.Autonaming.NameGenotype(_dataService.Genotypes.Select(x => x.Name).ToList(), specArr[0].Trim(), specArr[1], out specName);
                            if (nameFound)
                            {
                                GenotypeDto specDto = new GenotypeDto
                                {
                                    ProjectId = _dataService.LoadedProject.Id,
                                    Genus = genus,
                                    Species = species,
                                    Name = specName
                                };
                                if (specDto.HasValidationErrors)
                                {
                                    ia.Action = ImportAction.Fail;
                                    ia.Message = specDto.ValidationErrors.First().ErrorMessage;
                                }
                            }
                            else
                            {
                                ia.Action = ImportAction.Fail;
                                ia.Message = "Couldn't find a name for species.";
                            }



                        }
                    }
                    DispatcherHelper.CheckBeginInvokeOnUI(() =>
                    {
                        Operations.Add(ia);
                    
                    });
                }


            });
            return;
        }


    }
}
